package net.lindeer.lang

import java.io.BufferedReader
import java.io.IOException
import java.io.InputStreamReader
import java.nio.charset.Charset
import java.nio.file.Files
import java.nio.file.Paths
import kotlin.system.exitProcess

class Rox {
    companion object {

        private var hadError = false
        private var hadRuntimeError = false
        private val interpreter = Interpreter()

        @JvmStatic
        fun main(args: Array<String>) {
            when {
                args.size > 1 -> {
                    println("Usage: krox [script]")
                    exitProcess(64)
                }
                args.size == 1 -> {
                    runFile(args[0])
                }
                else -> runConsole()
            }
        }

        @Throws(IOException::class)
        private fun runFile(file: String) {
            val bytes = Files.readAllBytes(Paths.get(file))
            runSource(String(bytes, Charset.defaultCharset()))
            if (hadError) {
                exitProcess(65)
            }
            if (hadRuntimeError) {
                exitProcess(70)
            }
        }

        @Throws(IOException::class)
        private fun runConsole() {
            val reader = BufferedReader(InputStreamReader(System.`in`))

            fun readLine(): String? {
                print("> ")
                return reader.readLine()
            }

            lateinit var line: String
            while (readLine()?.let { line = it } != null) {
                runSource(line)
                hadError = false
            }
        }

        private fun runSource(source: String) {
            val scanner = Scanner(source)
            val tokens = scanner.scanTokens()
            val parser = Parser(tokens)
            val statements = parser.parse()
            if (hadError) {
                return
            }
            val resolver = Resolver(interpreter)
            resolver.resolve(statements)
            if (hadError) {
                return
            }
            interpreter.interpreter(statements)
        }

        fun error(line: Int, message: String) {
            report(line, "", message)
        }

        private fun report(line: Int, where: String, message: String) {
            println("[line $line] Error $where: $message")
            hadError = true
        }

        fun error(token: Token, message: String) {
            if (token.type == Token.Type.EOF) {
                report(token.line, " at end", message)
            } else {
                report(token.line, " at '${token.lexeme}'", message)
            }
        }

        fun runtimeError(error: RuntimeError) {
            println("${error.message}\n[line ${error.token.line}]")
            hadRuntimeError = true
        }
    }
}
